/**
 * 200905：创建
 * 201013：默认值处理
 * 20220304:[require] to [import]...jordia
 */
import {ShaderMaterial,Material,UnsignedByteType,Color,ShaderLib,UniformsUtils,Vector2} from 'three';
import {GPUComputationRenderer} from "three/examples/jsm/misc/GPUComputationRenderer";


export default class LightSpotFadeMaterial extends ShaderMaterial{
    constructor(renderer,values={},resolutionX=256,resolutionY=256){
        super();
        this._renderer=renderer;
        this._resolutionX=resolutionX;
        this._resolutionY=resolutionY;
        this._values=values;
        this.init();
    }

    //------------------------------------------对外属性及方法-----------------------------------------

    /**
     * 属性赋值
     * @param values {object} meshBasicMaterial所支持的属性及此材质特有的属性
     */
    set values(values){
        for(let key in values){
            if(this.hasOwnProperty(''+key+'')){
                if(values[''+key+'']!==undefined){
                    this[''+key+'']=values[''+key+''];
                }
            }else{
                console.warn(''+key+' is not a property of LightSpotFadeMaterial!');
            }
        }
        this.needsUpdate=true;
    }

    /**
     * 获取属性对象
     * @returns {*}
     */
    get values(){
        return this._paras;
    }

    /**
     * 拷贝
     * @param source
     * @returns {LightSpotFadeMaterial}
     */
    copy(source){
        Material.prototype.copy.call( this, source );
        this.values=source.values;
        return this;
    }

    /**
     * 克隆
     * @returns {LightSpotFadeMaterial}
     */
    clone() {
        let c=new LightSpotFadeMaterial();
        c.copy(this);
        return c;
    }

    /**
     * 销毁
     */
    dispose() {
        super.dispose();
        for(let rt of this._addMap.renderTargets){
            rt.dispose();
        }
        this._addMap=null;
        for(let rt of this._finMap.renderTargets){
            rt.dispose();
        }
        this._finMap=null;
        this._gpu=null;
        this._values=null;
        this._paras=null;
        this._exclusive=null;
        this._null.dispose();
        this.getter=null;
        this.setter=null;
        this._renderer=null;
        this._resolutionX=null;
        this._resolutionY=null;
    }

    //------------------------------------------私有属性及方法-------------------------------------------

    init(){
        this.initVariable();
        this.initGetter();
        this.initSetter();
        this.initProperty();
        this.initShaders();
        this.initUniforms();
        this.initGPUCR();
        this.initialize();
    }
    initVariable(){
        this._null=null;
        this._gpu=null;
        this._finMap=null;
        this._addMap=null;
        this._paras={};
        this._exclusive={
            background:{value:this._values.background||new Color('#4a4a45')},
            point:{value:this._values.point||new Vector2()},
            decay:{value:this._values.decay||0.95},
            fade:{value:this._values.fade||0.003},
            focus:{value:this._values.focus||100},
            strength:{value:this._values.strength||1},
        };
        this.getter={};
        this.setter={};
    }
    initSetter(){
        this.setter.default=(key)=> {
            return ()=> {
                if (this.uniforms.hasOwnProperty(key)) {
                    return this.uniforms[key].value;
                }
                return this._paras[key];
            };
        };
        this.setter.map=(key)=> {
            return ()=> {
                return this._paras[key];
            };
        };
        this.setter.background=(key)=>{
            return ()=>{
                return this._finMap.material.uniforms[key].value;
            };
        };
        this.setter.point=(key)=>{
            return ()=>{
                if (this._addMap) {
                    return this._addMap.material.uniforms[key].value;
                }
                return this._paras[key];
            };
        };
        this.setter.decay=(key)=>{
            return ()=>{
                if (this._addMap) {
                    return this._addMap.material.uniforms[key].value;
                }
                return this._paras[key];
            };
        };
        this.setter.fade=(key)=>{
            return ()=>{
                if (this._addMap) {
                    return this._addMap.material.uniforms[key].value;
                }
                return this._paras[key];
            };
        };
        this.setter.focus=(key)=>{
            return ()=>{
                if (this._addMap) {
                    return this._addMap.material.uniforms[key].value;
                }
                return this._paras[key];
            };
        };
        this.setter.strength=(key)=>{
            return ()=>{
                if (this._addMap) {
                    return this._addMap.material.uniforms[key].value;
                }
                return this._paras[key];
            };
        };
    }
    initGetter() {
        this.getter.default = (key) => {
            return (v) => {
                if (this.uniforms.hasOwnProperty(key)) {
                    this.uniforms[key].value = v;
                }
                this._paras[key] = v;
            };
        };
        this.getter.point = (key) => {
            return (v) => {
                this._addMap.material.uniforms[key].value.copy(v);
                this._paras[key] = v;
                this.render();
            };
        };
        this.getter.decay = (key) => {
            return (v) => {
                let _v=Math.max(Math.min(1,v),0);
                this._addMap.material.uniforms[key].value=_v;
                this._paras[key] = _v;
                this.render();
            };
        };
        this.getter.fade = (key) => {
            return (v) => {
                let _v=Math.max(Math.min(1,v),0);
                this._addMap.material.uniforms[key].value=_v;
                this._paras[key] = _v;
                this.render();
            };
        };
        this.getter.focus = (key) => {
            return (v) => {
                let _v=Math.max(v,1);
                this._addMap.material.uniforms[key].value=_v;
                this._paras[key] = _v;
                this.render();
            };
        };
        this.getter.strength = (key) => {
            return (v) => {
                let _v=Math.abs(v);
                this._addMap.material.uniforms[key].value=_v;
                this._paras[key] = _v;
                this.render();
            };
        };
        this.getter.background = (key) => {
            return (v) => {
                this._finMap.material.uniforms[key].value.copy(v);
                this._paras[key] = v;
                this.render();
            };
        };
        this.getter.diffuse = (key) => {
            return (v) => {
                this._addMap.material.uniforms[key].value.copy(v);
                this._paras[key] = v;
                this.render();
            };
        };
        this.getter.map = (key) => {
            return (v) => {
                let _v=v?v:this._null;
                this.uniforms[key].value=_v;
                this._paras[key] = _v;
                this.render();
            };
        };
    }
    getGetter(key){
        if(this.getter.hasOwnProperty(key)){
            return this.getter[key](key);
        }else{
            return this.getter.default(key);
        }
    }
    getSetter(key){
        if(this.setter.hasOwnProperty(key)){
            return this.setter[key](key);
        }else{
            return this.setter.default(key);
        }
    }
    definePropertyByObject(obj){
        for(let key of Reflect.ownKeys(obj)){
            if(key!=='constructor'&&key!=="prototype"&&key!=="name"&&key!=='id'&&key!=='uuid'){
                let ds=Object.getOwnPropertyDescriptor(obj,key);
                this._paras[key]=ds.value.value;
                let fc={
                    enumerable: true,
                    configurable: true,
                    set:this.getGetter(key),
                    get:this.getSetter(key)
                };
                Object.defineProperty(this,key,fc);
            }
        }
    }
    initProperty(){
        this.definePropertyByObject(ShaderLib.basic.uniforms);
        this.definePropertyByObject(this._exclusive);
        this.type='LightSpotFadeMaterial';
    }
    initShaders(){
        this.vertexShader=ShaderLib.basic.vertexShader;
        this.fragmentShader=ShaderLib.basic.fragmentShader;
    }
    initUniforms(){
        this.uniforms=UniformsUtils.merge( [ShaderLib.basic.uniforms] );
        this._exclusive=null;
    }
    initGPUCR(){
        this._gpu=new GPUComputationRenderer(this._resolutionX,this._resolutionY,this._renderer);
        this._gpu.setDataType(UnsignedByteType);

        let addMap=this._gpu.createTexture();
        this._null=addMap.clone();
        this._addMap=this._gpu.addVariable('addMap',this.addShader(),addMap);
        this._addMap.material.uniforms.diffuse={value:this._values.diffuse||new Color('#1e4a63')};
        this._addMap.material.uniforms.point={value:this._paras.point};
        this._addMap.material.uniforms.spotMap={value:this._null};
        this._addMap.material.uniforms.decay={value:this._values.decay||0.95};
        this._addMap.material.uniforms.fade={value:this._values.fade||0.003};
        this._addMap.material.uniforms.focus={value:this._values.focus||100};
        this._addMap.material.uniforms.strength={value:this._values.strength||1};

        let finMap=this._gpu.createTexture();
        this._finMap=this._gpu.addVariable('finMap',this.finShader(),finMap);
        this._gpu.setVariableDependencies(this._finMap,[this._addMap]);
        this._finMap.material.uniforms.map={value:this._null};
        this._finMap.material.uniforms.background={value:this._values.background||new Color('#646764')};

        this._paras.map=this._null;

        let err=this._gpu.init();
        if(err){
            console.error(err);
        }
    }
    initialize(){
        this.values=this._values;
        this._values=null;
    }
    render(){
        this._addMap.material.uniforms.spotMap.value=this._gpu.getCurrentRenderTarget(this._addMap).texture;
        this._finMap.material.uniforms.map.value=this._paras.map;
        this._gpu.compute();
        this.uniforms.map.value=this._gpu.getCurrentRenderTarget(this._finMap).texture;
    }
    addShader(){
        return [
            'uniform sampler2D spotMap;' ,
            'uniform vec3 diffuse;' ,
            'uniform vec2 point;' ,
            'uniform float decay;' ,
            'uniform float fade;' ,
            'uniform float focus;' ,
            'uniform float strength;' ,
            '#define RX '+(1/this._resolutionX).toFixed(6)+'' ,
            '#define RY '+(1/this._resolutionY).toFixed(6)+'' ,
            'void main(){' ,
            '   vec2 ruv=vec2(gl_FragCoord.x*RX,gl_FragCoord.y*RY);' ,
            '   vec2 vuv=ruv-0.5;' ,
            '   float dist=1.;' ,
            '   vuv-=point;' ,
            '   vuv*=2.;' ,
            '   dist=dot(vuv,vuv);' ,
            '   dist=1.-clamp(dist*focus,0.,1.);' ,
            '   dist*=dist;' ,
            '   vec3 mCol=diffuse*dist*strength;' ,
            '   vec4 texCol=texture2D(spotMap,ruv)*decay-fade;' ,
            '   gl_FragColor=vec4(mCol.rgb+texCol.rgb,1.);' ,
            '}'
        ].join('\n');
    }
    finShader(){
        return [
            'uniform sampler2D map;' ,
            'uniform vec3 background;' ,
            '#define RX '+(1/this._resolutionX).toFixed(6)+'' ,
            '#define RY '+(1/this._resolutionY).toFixed(6)+'' ,
            '#define EPS 0.000001' ,
            'void main(){' ,
            // '   vec2 ruv=gl_FragCoord.xy/resolution.xy;' ,
            '   vec2 ruv=vec2(gl_FragCoord.x*RX,gl_FragCoord.y*RY);' ,
            '   vec4 texCol=texture2D(map,ruv);' ,
            '   if(dot(texCol.rgb,texCol.rgb)<EPS){' ,
            '       texCol=vec4(1.);' ,
            '   }' ,
            '   vec4 difCol=texture2D(addMap,ruv);' ,
            '   gl_FragColor=vec4(texCol.rgb*background+difCol.rgb,1.);' ,
            '}'
        ].join('\n');
    }
}

